The use of Images with PHP - Example 1

Introduction

One of the features of the WWW that led to its rapid adoption was the ability to include images in WWW pages. PHP has extensive facilities to handle images.

This first example is built round a very simple facility to determine the name and capital of a European country given it's top-level DNS code (e.g. "de" for Germany). On its own this would be a not very interesting or exciting example of accessing a database via PHP. What makes this example interesting is that it also shows the flag of the country.

Try it now

General points

You will have assumed that all the information is stored in a database and you'll probably have guessed that it's a MySQL table. Here's the schema for the euinfo table.

mysql>describe euinfo;
+---------+-------------+------+-----+---------+-------+
| Field   | Type        | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| name    | varchar(50) | YES  |     | NULL    |       |
| capital | varchar(50) | YES  |     | NULL    |       |
| flag    | blob        | YES  |     | NULL    |       |
| dns     | char(2)     | YES  |     | NULL    |       |
+---------+-------------+------+-----+---------+-------+
4 rows in set (0.00 sec)

The interesting field is flag. This is a BLOB, an abbreviation for binary large object. Most databases support BLOBs which are intended for the storage of unstructured "chunks" of data. MySQL has several sizes of BLOB, the basic one here will store up to 65536 bytes of data. [A quick examination of the flag image files showed an average size around 5K so this is quite OK. For other applications you'd have to check the actual image sizes and use an appropriate flavour of BLOB.]

It should be noted that many authors do not recommend storing image data in a database in this way for reasons basically associated with database and query efficiency. The preference is to store the images in separate files and store the file path names in the database.

Querying the database

Here's the HTML of the basic query page.

<html>
<head>
<title>
European Information Test
</title>
</head>
<body>
<h1>Information Test</h1>
<p>
Enter DNS top level domain for a European country (e.g. "de")
<form method=post action=eudns.php >
<input type=text name=dns>Name<br>
<input type=submit value="Process Query">
</form>
</body>
</html>

If you're familiar with the HTML forms mechanism this will pose few problems. Here's the basic backend (eudns.php) that processes the query.

<html>
<head>
<title>
European DNS domain lookup
</title>
</head>
<body>
<h1>DNS lookup for
<?
	$dns = $_REQUEST['dns'];
	print $dns;
	print "</h1>";
	$dbconn = mysql_connect("clun.scit.wlv.ac.uk","jphb","mumble");
	mysql_select_db("mydatabase");
	$sql = "SELECT capital,name FROM euinfo WHERE dns = '" . $dns . "'";
	$result = mysql_query($sql);
	$nrows = mysql_num_rows($result);
	if($nrows == 1)
	{
		$row = mysql_fetch_array($result);
		$name = $row["name"];
		print "Name = " . $name . "<br>";
		print "Capital = " . $row["capital"] . "<br>";
		print "Flag = <img src = getflag.php?dns=" . $dns . "><br>";
	}
	else
		print "No data available";
?>
</h1>
</body>
</html>

Again if you're familiar with PHP scripts for processing queries, this should be quite straightforward. The most interesting line is

print "Flag = <img src = getflag.php?dns=" . $dns . "><br>";

When the query is processed this results in a line something like

<img src = getflag.php?dns=fi >

being written to the output. The img tag is, of course, associated with the source of an image as an attribute. In this case the image source is another PHP script with the request part dns=fi. Here's the getflag.php script.

<?
	header("Content-type: image/gif");
	$dns = $_REQUEST['dns'];
	$dbconn = mysql_connect("clun.scit.wlv.ac.uk","jphb","mumble");
	mysql_select_db("mydatabase");
	$sql = "SELECT flag FROM euinfo WHERE dns = '" . $dns . "'";
	$result = mysql_query($sql);
	$nrows = mysql_num_rows($result);
	if($nrows == 1)
	{
		$row = mysql_fetch_array($result);
		echo $row['flag'];
		
	}
?>

This is more interesting. The first important step is to ensure that the browser recognises the data stream from the PHP scripting engine as an image rather than text. Normally browsers rely on the Content-type HTTP response header and WWW servers generate such a header by examining the final part of the file name. In this case the server would see ".php" and probably say something like text/html which would be incorrect. The PHP function header() can be used to specify any HTTP response header, in this case it indicates to the browser that the data stream is a GIF-encoded image. It is important to get the syntax of the HTTP response header absolutely correct.

It is important to remember that the data stream contains arbitrary characters (sometimes called a binary stream). The PHP line

	$sql = "SELECT flag FROM euinfo WHERE dns = '" . $dns . "'";

constructs the SQL command necessary to get the image from the database. The lines after this are conventional PHP MySQL query processing. The remaining important point is to ensure that the data recovered from the database is sent "up-the-wire" exactly as read from the database. The PHP standard command echo will do this; print expects its input data to be strings encoded in accordance with certain conventions, echo does not impose any such constraints.

Getting the images into the database

You may be wondering how the image data was loaded into the database. Doing this poses some problems. The major problem concerns the arbitrary data stream that is to be loaded. This can be done using the MySQL command line client but, not surprisingly, special characters (such as string terminators, new lines etc.,) that appear in the data have to be escaped in accordance with MySQL conventions. Rather than writing a small programme to convert image data to such escaped strings it was simpler to develop an interactive facility that worked via the WWW. This shows a number of interesting features.

The basic data uploading HTML is.

<html>
<head>
<title>
European Information Entry
</title>
</head>
<body>
<h1>Information Entry</h1>
<form method=post action=euinfo.php enctype="multipart/form-data">
<input type=text name=name>Name<br>
<input type=text name=capital>Capital<br>
<input type=text name=dns>DNS<br>
<input type=file name=flag>
<input type=submit value="Submit data">
</form>
</body>
</html>

This is, again, conventional familiar coding apart from the form line and the penultimate input line. The form line

<form method=post action=euinfo.php enctype="multipart/form-data">

includes the possibly unfamiliar attribute enctype set to the value multipart/form-data. This specifies how the browser is to encode data that is being transferred to the server, the value multipart/form-data is required if the contents of files are to be uploaded. The setting of the method attribute's value to post is also required for file uploading.

The input line

<input type=file name=flag>

implies file selection. When this is rendered by the browser, the user will see a text input box in which he/she can type a local file name. The user will also probably see a browse button which allows the user to browse the local file system and select a file for uploading.

Once the data has reached the server the following PHP script processes the data. Most of this is familiar query backend processing.

<html>
<head>
<title>
Data Entered
</title>
</head>
<body>
<h1>Data Entered
<?
	$dns = $_REQUEST['dns'];
	$name = $_REQUEST['name'];
	$capital = $_REQUEST["capital"];
	$flag = $_REQUEST["flag"];
	print $name . "</h1><p>\n";
	print "<pre>";
	print "File name   = ". $_FILES['flag']['name'] . "<br>\n";
	print "Mime type   = ". $_FILES['flag']['type'] . "<br>\n";
	print "File size   = ". $_FILES['flag']['size'] . "<br>\n";
	print "Local name  = ". $_FILES['flag']['tmp_name'] . "<br>\n";
	print "Error code  = ". $_FILES['flag']['error'] . "<br>\n";
	print "</pre>";
	$ffile = fopen($_FILES['flag']['tmp_name'],"rb");
	$contents = fread($ffile,$_FILES['flag']['size']);
	$dbconn = mysql_connect("clun.scit.wlv.ac.uk","jphb","mumble");
	mysql_select_db("mydatabase");
	$eflag = mysql_escape_string($contents);
	
	$sql = "INSERT INTO euinfo SET name = '" . $name . "', capital = ' " .
			$capital . "', dns = '" . $dns . "', flag = '" .
			$eflag . "'";
	mysql_query($sql);
	print "<p>Length of escaped flag string = " . strlen($eflag) . "</p>";
?>
</body>
</html>

The print statements near the start of the script are debugging and can safely be omitted in a production application. What actually happens is that the contents of the uploaded file are saved on the server in a temporary file. Information about the uploaded file is stored in the global PHP array $_FILES indexed both by file name and information type. The item that really matters is

$_FILES['flag']['tmp_name']

This gives the name of the local server side file that is, temporarily, holding the up-loaded data. This file is deleted as soon as the script exits. The actual name is likely to be something like /var/tmp/phpXqkg. Once the script has determined the name of the local file the file is opened, it's contents read into memory, converted into a MySQL escaped string and incorporated into an SQL statement.

$ffile = fopen($_FILES['flag']['tmp_name'],"rb");

The PHP fopen() is closely modelled on the C function of the same name. The second parameter rb implies opening the file in binary read mode. Binary mode implies that the data in the file is stored in memory exactly as read from the file. The value returned from fopen() is a PHP resource, a complex data item (an object) that contains all the information about the open file. The entire contents of the file are read into the PHP variable $contents using the function fread().

$contents = fread($ffile,$_FILES['flag']['size']);

The first parameter is the file resource that was returned by the fopen() function. The second parameter specifies the maximum amount of data to read, in this case it is set to the file size.

The follwing line

$eflag = mysql_escape_string($contents);

converts the data into a form suitable for inclusion in a MySQL SQL statement. [I.e. performs the standard escapes for special characters.] The SQL statement is now constructed and executed thus

$sql = "INSERT INTO euinfo SET name = '" . $name . "', capital = ' " .
		$capital . "', dns = '" . $dns . "', flag = '" .
		$eflag . "'";
mysql_query($sql);

and that's about that.

Conclusion

This was a bit tricky to get going. The main problem is that once the browser is told to interpret the data stream as an image, PHP error messages aren't a great deal of use. In fact any message written to the output will cause the remote browser to complain that the image is faulty. For debugging the best approach is to comment out the header() and accept a large amount of gibberish where the image is incorrectly interpreted as text.

The code shown here should include more error checking and it should be realised that allowing arbitrary uploading of files to a server is a potential security problem. Uploaded files should be carefully vetted before anything is done with them.


Other Notes and examples of the use of PHP

Author : Peter Burden